#include <stdio.h>

/*
 * factorial (阶乘)
 *
 * 类似文章:
 * tecknic\book-sotfware\ASM\论程序底层优化的一些方法与技巧.pdf
 */


/* 禁止GCC内联 */
__attribute__((noinline))
int calc(int n)
{
    int fact0 = 1, fact1 = 1, fact2 = 1, fact3 = 1;
    int i;

    for (i = 0; i < n; i += 4) {
        fact0 *= i;
        fact1 *= i + 1;
        fact2 *= i + 2;
        fact3 *= i + 3;
    }

    return fact0 * fact1 * fact2 * fact3;
}

int main()
{
    return calc(1000000000);
}

#if 0

    假设n总是4的倍数。如果要处理n不是4的倍数的情况，只需要在主循环体外，
    增加一个小的循环单独处理最后的n%4个数，也就是最多3个数即可，对整体性能影响几乎为0.

    通过对循环进行4次展开，之前每次循环执行1次乘法，现在每次循环执行4次，这就带来了三点很重要的变化：

    1. 循环次数减少75%，无用指令减少了，相应的CPU执行这些指令本身的开销也少了。
    2. 计算过程中，热点路径的条件跳转指令少了75%，这样就减少了由于控制相关引起的流水线冲突，提升了流水线执行的效率。
    3. 提升了指令的并行度，使得CPU superscalar的技术得到更充分的发挥，提高了每个时钟周期并行执行指令的条数。

/*
 * 循环展开
 */
    循环展开是一种非常重要的优化方法，也是编译器后端中常用的一种优化方式，
    它可以通过减少热点路径上的“无用指令”以及分支指令的个数，
    来更好地发挥CPU指令流水线的指令并行执行能力，从而提高程序整体性能。

    很多时候，可以借助编译器来帮我们实现这种优化，但编译器也有失效的时候，
    比如这个例子。这时，我们就不得不手动来进行循环展开来优化程序性能。
    循环展开时，必须尽量减少语句间的相互依赖。

    此外，循环展开的次数并没有一个固定的公式，需要根据具体代码和CPU来决定，
    通常需要多次尝试来找到一个最优值。

    不过，手动循环展开往往是以牺牲代码可读性为代价的，因此使用时也做好取舍。
    此外，循环展开还会在一定程度上增加程序代码段的大小，还可能会影响到程序局部性，对cache产生影响，
    因此使用时候，要仔细权衡。



https://mp.weixin.qq.com/s?__biz=MzI2NTI3NDY0OA==&mid=2247484005&idx=1&sn=40fcca0898b450cb153054a5f3eb51d1&chksm=ea9e9612dde91f0485b7eb76ec4e86fbbe838e6a65eb43790add603420c47c17f16d97dc464b&scene=126&sessionid=1701674629#rd
https://mp.weixin.qq.com/s?__biz=MzI2NTI3NDY0OA==&mid=2247483917&idx=1&sn=f08296811648f5d24ead04c51661a793&chksm=ea9e967adde91f6c580d48d01021ae553122d0b6047e73868be6e73df85e555addb0713d202b&scene=21#wechat_redirect
https://mp.weixin.qq.com/s?__biz=MzI2NTI3NDY0OA==&mid=2247483937&idx=1&sn=85649b05fd0846342dd7db240ba81fdc&chksm=ea9e9656dde91f405d3d2019eb783acac5531273ca3470a269a128484a13e8072ade44bb98b0&scene=21#wechat_redirect

#endif
